/*
 * JAVE - A Java Audio/Video Encoder (based on mysqldump)
 *
 * Copyright (C) 2008-2009 Carlo Pelliccia (www.sauronsoftware.it)
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.vrv.mysqlback;


import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static com.vrv.mysqlback.MysqldumpConstants.*;

/**
 * @author Bang
 */
public class Backup {

    private static final Logger log = LoggerFactory.getLogger(Backup.class);

    private ThreadPoolExecutor poolExecutor = null;

    private static final Pattern INSECURE_PASS_REGEX = Pattern.compile("mysqldump.*\\[Warning\\].*password.*insecure.*", Pattern.CASE_INSENSITIVE);
    private static final Pattern GET_ERROR_REGEX = Pattern.compile("mysqldump: (Got error:.*)", Pattern.CASE_INSENSITIVE);

    /**
     * The locator of the mysqldump executable
     */
    private MysqldumpLocator locator;

    /**
     * It builds an encoder using a {@link DefaultMysqldumpLocator} instance to
     * locate the mysqldump executable to use.
     */
    public Backup() {
        this.locator = new DefaultMysqldumpLocator();
        this.poolExecutor = new ThreadPoolExecutor(2, Runtime.getRuntime().availableProcessors(),
                120L, TimeUnit.SECONDS,
                new ArrayBlockingQueue<Runnable>(100),
                new ThreadFactory() {

                    private AtomicInteger atomicCount = new AtomicInteger(0);

                    @Override
                    public Thread newThread(Runnable r) {
                        Thread thread = new Thread();
                        thread.setDaemon(true);
                        thread.setName("mysqldump-backup-pool-" + atomicCount.getAndIncrement());
                        return thread;
                    }
                }
        );
    }

    /**
     * It builds an encoder with a custom {@link MysqldumpLocator}.
     *
     * @param locator The locator picking up the mysqldump executable used by the
     *                encoder.
     */
    public Backup(MysqldumpLocator locator) {
        this.locator = locator;
        this.poolExecutor = new ThreadPoolExecutor(2, Runtime.getRuntime().availableProcessors(),
                120L, TimeUnit.SECONDS,
                new ArrayBlockingQueue<Runnable>(100),
                new ThreadFactory() {

                    private AtomicInteger atomicCount = new AtomicInteger(0);

                    @Override
                    public Thread newThread(Runnable r) {
                        Thread thread = new Thread();
                        thread.setDaemon(true);
                        thread.setName("mysqldump-backup-pool-" + atomicCount.getAndIncrement());
                        return thread;
                    }
                }
        );
    }

    /**
     * It builds an encoder with a custom {@link MysqldumpLocator}.
     *
     * @param locator The locator picking up the mysqldump executable used by the
     *                encoder.
     */
    public Backup(MysqldumpLocator locator, ThreadPoolExecutor threadPoolExecutor) {
        this.locator = locator;
        this.poolExecutor = threadPoolExecutor;
    }

    /**
     * mysqldump  -P3306 -uroot -proot --compact jsoc > database.sql
     *
     * @param attributes
     * @throws IllegalArgumentException
     * @throws BackupException
     * @throws BackupException
     */
    public MysqldumpInfo backup(MysqldumpAttributes attributes)
            throws IllegalArgumentException, BackupException {
        if (attributes == null) {
            throw new IllegalArgumentException(
                    "attributes can not be null");
        }
        String host = attributes.getHost();
        String username = attributes.getUsername();
        String password = attributes.getPassword();
        String databases = attributes.getDatabases();
        String dumpPath = attributes.getDumpPath();
        String dumpName = attributes.getDumpName();
        Integer port = attributes.getPort();
        String tables = attributes.getTables();
        MysqldumpExecutor mysqldump = locator.createExecutor();
        mysqldump.addDefaultArgs(MysqldumpConstants.COMPACT, MysqldumpConstants.COMPRESS, MysqldumpConstants.SKIP_COMMENTS);
        if (host != null) {
            mysqldump.addArgument(String.format(MysqldumpConstants.HOST_TEMPLATE, host));
        }
        if (username != null) {
            mysqldump.addArgument("-u" + username);
        }
        if (password != null) {
            mysqldump.addArgument("-p" + password);
        }
        if (port != null) {
            checkPort(port);
            mysqldump.addArgument("-P" + port);
        }
        if (databases != null) {
            mysqldump.addArgument("-B");
            mysqldump.addArgument(databases);
        } else {
            mysqldump.addArgument(MysqldumpConstants.ALL_DATABASES);
        }
        if (tables != null) {
            mysqldump.addArgument(MysqldumpConstants.TABLES);
        }

        if (dumpPath == null) {
            dumpPath = MysqldumpAttributes.DEFAULT_DUMPPATH;
        }
        File file = new File(dumpPath);
        if (!file.exists()) {
            file.mkdirs();
        }
        if (dumpName == null) {
            dumpName = System.currentTimeMillis() + ".sql";
        }
        String dumpFilePath = file.getAbsolutePath() + File.separator + dumpName;
        mysqldump.addArgument("--result-file=" + dumpFilePath);

        try {
            mysqldump.execute();
        } catch (IOException e) {
            throw new BackupException(e);
        }
        try {
            FutureTask<List<String>> errorStreamFuture = new FutureTask<>(new HandlerInputStreamCallable(mysqldump.getErrorStream()));
            new Thread(errorStreamFuture).start();
            List<String> inputMsgList = handlerSuccessInputStream(mysqldump.getInputStream());
            MysqldumpInfo mysqldumpInfo = new MysqldumpInfo();
            mysqldumpInfo.setCommand(mysqldump.getCommand());
            mysqldumpInfo.setDumpPath(dumpFilePath);
            if (!inputMsgList.isEmpty()) {
                StringBuilder stringBuffer = new StringBuilder();
                for (String message : inputMsgList) {
                    stringBuffer.append(message);
                }
                mysqldumpInfo.setInfoMsg(stringBuffer.toString());
            }
            List<String> messageList = errorStreamFuture.get();
            if (messageList == null || messageList.isEmpty()) {
                mysqldumpInfo.setSuccess(true);
            } else {
                StringBuilder stringBuffer = new StringBuilder();
                for (String message : messageList) {
                    Matcher matcher = GET_ERROR_REGEX.matcher(message);
                    if (matcher.find()) {
                        // 匹配ERROR
                        stringBuffer.append(matcher.group(1)).append("\r\n");
                    }
                }
                String string = stringBuffer.toString();
                if (string.endsWith("\r\n")) {
                    string = string.substring(0, string.lastIndexOf("\r\n"));
                }
                if (string.isEmpty()) {
                    mysqldumpInfo.setSuccess(true);
                } else {
                    mysqldumpInfo.setSuccess(false);
                    mysqldumpInfo.setErrorMsg(string);
                }
            }

            return mysqldumpInfo;
        } catch (ExecutionException | InterruptedException e) {
            throw new BackupException(e);
        } finally {
            mysqldump.destroy();
        }
    }

    private List<String> handlerSuccessInputStream(InputStream inputStream) {
        List<String> msg = new ArrayList<>();
        try {
            BufferedReader bReader = new BufferedReader(new InputStreamReader(new BufferedInputStream(inputStream), StandardCharsets.UTF_8));
            String line;
            while ((line = bReader.readLine()) != null) {
                msg.add(line);
            }
            bReader.close();
        } catch (Exception ex) {
            log.error("处理流数据异常", ex);
        }
        return msg;
    }


    private void checkPort(Integer port) throws BackupException {
        if (port < MIN_PORT || port > MAX_PORT) {
            throw new BackupException("端口非法");
        }
    }


}
